#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "structures.h"
#include "mod_file.h"
#include "mod_network.h"
#include "mod_protocol.h"
#include "mod_config.h"
#include <glib-2.0/glib.h>
extern PeerConfig *pconf;
//sono di SDrive
void* handleOperation(int connsd){
    //read first sizeof(int) bits from the connection socket and select the
    //appropiate operation
    int n,operation=-1;
    printf("connsd: %d\n", connsd);
    n=readn(connsd,&operation,sizeof(int));
    if(operation==15){
        printf("E' una send_chunk\n");
        handle_send_chunk(&connsd);
    }
    if(operation==16){
        //questa operazione avviene in modo anormale poichè è conseguenza
        //diretta di una request_send_chunk. Il return è per far morire il processo
        //senza thread. In caso di thread va rimosso.
        printf("E' una receive_chunk_1\n");
        handle_receive_chunk_1(&connsd);
        return;
    }
    if(operation==19){
        printf("E' una ReceivePossList\n");
        handle_peer_receive_poss_list(&connsd);
    }
    if(operation==20){
        printf("E' una DeleteFile\n");
        handle_delete_file(&connsd);
    }
    if(operation==-1){
        printf("Errore\n");
    }
    pthread_detach(pthread_self());
    pthread_exit(NULL);
    return;
}
/*
 * Operazioni inviate da Peer a SuperPeer
 */

    int request_peer_join(){
        //se è la prima connessione devo segnarmi il tempo di primo accesso su
        //un file
        //se è la prima connessione devo segnarmi il tempo di primo accesso su
        //un file
        setLastUp();
        int load = getLoad();
        float ava;
        getDisp(&ava);
        printf("Dentro a request_peer_join, getAva ritorna: %lf\n",ava);
        int listenPeerPort = pconf->port;
        struct sockaddr_in addr;
        int sd = create_TCP_client_socket(pconf->spipaddress, pconf->spportlisten, &addr);
        connect_with_server(&sd, &addr);
        int op = 8;
        writen(sd,&op,sizeof(int));
        writen(sd,&load,sizeof(int));
        writen(sd,&ava,sizeof(float));
        writen(sd,&listenPeerPort,sizeof(int));
        //numero file completi
        int nFile = countAllFile();
        writen(sd,&nFile,sizeof(int));
        //invio l'hash dei file completi
        int nFileConfirm = 0; //evitabilissimo si possono accorpare la seguente
        //funzione con la precedente
        char * hashFile = readAllFile(&nFileConfirm);
        writen(sd,hashFile,sizeof(char)*strlen(hashFile));
        int nChunk = countAllChunk();
        //numero struct chunkLight
        //anche questa funzione è evitabilissima
        int nChunkLight=0;
        struct chunkLight *cl = (struct chunkLight*)readAllChunk(&nChunkLight);
        writen(sd,&nChunk,sizeof(int));
        writen(sd,cl,sizeof(struct chunkLight)*nChunk);
        //aspetto il messaggio di conferma
        int opConferma=1;
        readn(sd,&opConferma,sizeof(int));
        //è un messaggio di conferma
        readn(sd,&opConferma,sizeof(int));
        if(opConferma==0){
            printf("Operazione di Join effettuata correttamente sul SP\n");
        }
        close_socket(&sd);
        //creo connessione
        /*
         * int numerostrutture = numero chunk sul fs
         * chunkLight chunks[numerostrutture];
         * chunks = (chunkLight *)malloc(sizeof(chunkLlight)*numerostrutture);
         * int i = 0;
         *
         * for(i=0; i<numerostrutture; i++){
         * chiamo una funzione che legge cartella per cartella e per ogniuna la lista dei chunks
         * in serisco in nella strutture chunks: chunks->IDF = nomedirectory e chunks->ID = nomedelchunk ennesimo
         * in quelal directory
         * }
         * int operazione = 8;
         * fullwrite(fd, &operazione, sizeof(int));
         * fullwrite(fd, &load, sizeof(int));
         * fullwrite(fd, &ava, sizeof(double));
         * fullwrite(fd, &numerostrutture, sizeof(int));
         *
         *  for(i=0; i<numerostrutture; i++){
         *  invio sul socket (scrivo) ogni singola struttura per ogni elemento dell'array
         * usando la fullwrite del gapil.
         *  }
         *
         * mi metto in read e aspetto la conferma tramite messaggio generico.
         *
         */

        return 1;
    }

    int request_peer_leave(){
        printf("Dentro a proto_peer_leave\n");
        struct sockaddr_in addr;
        int sd = create_TCP_client_socket(pconf->spipaddress, pconf->spportlisten, &addr);
        connect_with_server(&sd, &addr);
        int operation = 9;
        writen(sd,&operation,sizeof(int));
        writen(sd,&pconf->port,sizeof(int));
        close_socket(&sd);
        setLastDown();
        //fullwrite(fd, &operation, sizeof(int));
        /*
         * aspetto che il sp mi mandi la conferma casomai lui si incasina
         * e resto appeso li!
         *
         * da network chiudi la connessione
         */
        return 1;
    }
    int request_peer_get(char *nomefile){
        int operazione = 10;
        int load = getLoad();
        float ava = getDisp();

      //  lunghezza = strlen(nomefile)-1;
    //    char nomefiletosend[lunghezza];

        /* per ogni campo: fullwrite()
         dopo che ho mandato tutto mi metto in ascolto
         e aspetto la lista dei possessori che mi arriva con il pacchietto
         RECEIVE_POSS_LIST (pacchetto id 19).
         Una volta ricevuto e letto... magari... 19 preparo le strutture
         
         * leggo tutti i dati del pacchetto uno per volta con la fullread della lunghezza che so
         * e li savo in delle variabili di appoggio, strutture comprese.
         *
         * Dichiaro un array di dimensione pari al numero di strutture (pari al numero di chunks),
         * mi leggo i pezzetti, l'hash   etc...
         *
         * Si passa st'array alla funzione de routing che te torna a lista dei perr da cu scaricà
         * ...scarica.... facendo richieste coi pacchetti contenenti un SEND_CHUNK ad ogni peer
         * da cui vuoi scaricare la robba (i peer ed i chunk li devi scegliere da quella lista),
         * si mette in attesa in lettura e aspetta il pacchetto RECEIVE_CHUNK (pacchetto id 16),
         * prepara le strutture, la directory e scrive i vari chunk sull'hd. Una volta completato
         * rilegge l'array contenente la lista dei chunk, ricostuisce il file e lo ricopia in incoming.
         *
         */

        return 1;
    }

    int request_peer_put(char *nomefile){
        
        struct sockaddr_in addr;

        int sd = create_TCP_client_socket(pconf->spipaddress, pconf->spportlisten, &addr);

        connect_with_server(&sd, &addr);

        int op=11;
        
        writen(sd,&op,sizeof(int));

        int load = getLoad();

        writen(sd,&load,sizeof(int));

        float ava = 0.0;

        getDisp(&ava);

        writen(sd,&ava,sizeof(float));

        writen(sd,&pconf->port,sizeof(int));

        splitFileAndSend(&sd,"/home/alex/SD/toPut/",nomefile);

        //aspetto la conferma

        int opConferma = -1;

        //controllo se il pacchetto giunto è un messaggio generico opID=0
        readn(sd,&opConferma,sizeof(int));

        if(opConferma!=0){
            
            return -1;
        }
        //controllo che il contenuto del pacchetto sia 0. Vuole dire che
        //la put è stata effettuata con successo. Riuso opConferma.

        readn(sd, &opConferma, sizeof(int));

        if(opConferma!=0){
            return -2;
        }
        
        close_socket(&sd);

        return 1;
    }

    int request_peer_remove(char *nomeFile){

        /*
         * legge load, ava, strl nomefile, nomefile.
         * Invia un paccheto REMOVE (pacchetto id 12) contenente
         * idpacchetto, load, ava, srlen nomefile, nomefile
         * Aspetti una conferma dal SP.
         *
         */
        int port = 5193;

        struct sockaddr_in addr;

        int sd = create_TCP_client_socket(pconf->spipaddress, port, &addr);

        connect_with_server(&sd, &addr);

        int op=12;

        writen(sd,&op,sizeof(int));

        int load = getLoad();

        writen(sd,&load,sizeof(int));

        float ava;

        getDisp(&ava);

        writen(sd,&ava,sizeof(float));

        int lunghezzaNome = strlen(nomeFile);

        writen(sd,&lunghezzaNome,sizeof(int));

        writen(sd,nomeFile,lunghezzaNome*sizeof(char));

        close_socket(&sd);
        
        return 1;
    }

    int request_peer_list(){
        /*
         * prendo load e ava
         * invio un pacchetto LIST (pacchetto id 13)
         * contentente 13, load, ava.
         * Mi metto in attesa di RESPONSE_LIST, legge 18,
         * il numero di files nella lista,
         * l'intero con la lunghezza del nome del file,
         * il nome del file, questi due tante volte quante
         * il numero di files in lista.
         * printa a schermo.
         *
         */pconf = loadPeerConfig("peerconfig.conf");
        int load = getLoad();
        float ava = 0;
        getDisp(&ava);
        printf("Dentro a request_peer_list, getAva ritorna: %lf",ava);
        char ip[16]="127.0.0.1";
        int port=5193;
        struct sockaddr_in addr;
        int sd = create_TCP_client_socket(ip, port, &addr);
        connect_with_server(&sd, &addr);
        int op = 13;
        writen(sd,&op,sizeof(int));
        writen(sd,&load,sizeof(int));
        writen(sd,&ava,sizeof(double));
        close_socket(&sd);
    }

    int handle_peer_responseList(){

    }

    int handle_peer_receive_poss_list(int *sd){

        int dimNomeFile = 0;
        GList *listaRouting = NULL;
        readn((*sd),&dimNomeFile,sizeof(int));
        char * nomeFile = calloc(dimNomeFile+1,1);
        readn((*sd),nomeFile,sizeof(char)*dimNomeFile);
        char * IDF = calloc(33,1);
        readn((*sd),IDF,33);
        int nStrutturePeerPossChunk = 0;
        readn((*sd),&nStrutturePeerPossChunk,sizeof(int));
        struct peerPossessorChunk * ppc = calloc(nStrutturePeerPossChunk,sizeof(struct peerPossessorChunk));
        struct peerPossessorChunk * ppcCopy = ppc;
        int i=0, j=0, nPoss=0;
        GList *listaPeerPoss = NULL;
        GList *tmp = NULL;
        for(i=0;i<nStrutturePeerPossChunk;i++){

            readn((*sd),ppc->ID,sizeof(char)*32);

            ppc->ID[32]='\0';

            readn((*sd),&nPoss,sizeof(int));

            ppc->nS = nPoss;

            ppc->elr = calloc(nPoss,sizeof(struct elemRouting));

            struct elemRouting *saveelr = ppc->elr;

            for(j=0;j<nPoss;j++){

                //creo la struttura peerLight

                struct peerLight * tmpl = calloc(1,sizeof(struct peerLight));

                readn((*sd),tmpl,sizeof(struct peerLight));
                //a questo punto creo le strutture elemRouting

                struct elemRouting * elr = calloc(1,sizeof(struct elemRouting));

                elr->pl = tmpl;

                elr->chunkPosseduti = 1;

                elr->distance = 0.0;

                //controllo se l'elemento appena creato è già presente o meno
                //nella lista

                tmp = NULL;

                gconstpointer *gelr = (gconstpointer)elr;

                tmp = g_list_find(listaPeerPoss,gelr);

                //se tmp = NULL allora non è presente e posso inserirlo
                //altri menti vado avanti e lo scarto

                if(tmp==NULL){

                    listaPeerPoss = g_list_insert(listaPeerPoss,gelr,-1);

                    ppc->elr = elr;

                    ppc->elr++;
                }
                else{
                    //aggiungo il numero di chunk posseduti visto che è presente
                    //già nella lista

                    struct elemRouting *addChunk = (struct elemRouting *)g_list_nth_data(tmp,0);

                    addChunk->chunkPosseduti++;

                    ppc->elr=addChunk;

                    ppc->elr++;

                    free(elr);

                    free(tmpl);

                }
            }

            ppc->elr = saveelr;

            ppc++;

        }

        ppc = ppcCopy;

        close_socket(sd);
        
        //invio alla funzione di routing la lista dei elemRouting e la lista
        //dei chunk posseduti
        
        doRouting(listaPeerPoss,ppc,nStrutturePeerPossChunk);
        
        return 1;
    }

    /*Parte di comunicazione fra Peer e Peer
     Testata e funzionante*/
    int request_peer_Send_Chunk(struct chunkLight * cl, int numeroChunk, struct peerLight * pl){
        //apro la connessione con il peer
        //char ip[16]="127.0.0.1";//indirizzo peer
        //int port=5193;//porta peer
        struct sockaddr_in addr;
        int sd = create_TCP_client_socket(pl->addr, pl->port, &addr);
        connect_with_server(&sd, &addr);
        //connesso con il peer invio l'id dell'operazione
        int op=15;
        writen(sd,&op,sizeof(int));
        //invio il nome dell'hash file
        char hashFile[33];
        strcpy(hashFile,cl->IDF);
        writen(sd,hashFile,sizeof(char)*33);
        //invio il numero di chunk
        writen(sd,&numeroChunk,sizeof(int));
        //inizio ad inviare le hash dei chunk che verranno poi inviati dal
        //ricevente 
        int i=0;
        struct chunkLight * tmp=cl;
        for(i=0;i<numeroChunk;i++){
            writen(sd,tmp->ID,sizeof(char)*33);
            tmp=tmp+1;
        }
        //close_socket(&sd);
        //devo mettermi in attesa della risposta
        handleOperation(sd);
        return 1;
    }
    //testata e funzionante
    int request_receive_chunk_1(struct chunk *chks, int n, int *sd){
        //la struttura chks contiene n strutture chunk in cui sono compilati
        //solo i campi di IDF e ID
        int i=0;
        int op=16;
        writen((*sd),&op,sizeof(int));
        //invio il nome del file
        writen((*sd),chks->IDF,sizeof(char)*32);
        //invio numero dei chunk
        writen((*sd),&n,sizeof(int));
        //inizio la ricostruzione dei chunk sul file tramite un ciclo
        int lun=strlen(pconf->sharedir);
        char percorso1[32+lun];
        FILE *fp;
        char percorso2[65+lun];
        memset(percorso2,0,65+lun);
        strcpy(percorso1,pconf->sharedir);
        strcat(percorso1,chks->IDF);
       // strcat(percorso2,slash);
        struct chunk *tmp=chks;
        for(i=0;i<n;i++){
            strcpy(percorso2,percorso1);
            strcat(percorso2,"/");
            strcat(percorso2,tmp->ID);
            if((fp=fopen(percorso2,"rb"))==NULL){
                perror("Errore apertura file\n");
                exit(-1);
            }
            fseek(fp, 0, SEEK_END);
            tmp->size = ftell(fp);
            fseek(fp, 0, SEEK_SET);
            //rimepio la struttura chunk con il file
            memset(tmp->chk,0,10240);
            fread(tmp->chk,1,tmp->size,fp);
            fclose(fp);
            memset(percorso2,0,lun+65);
            tmp=tmp+1;
        }
        //invio tutto su socket
        writen((*sd),chks,sizeof(struct chunk)*n);
        close_socket(sd);
    }
    //testata e funzionante
    int handle_send_chunk(int *sd){
        //prendo l'hash del file
        char hashFile[33];
        readn((*sd),hashFile,sizeof(char)*33);
        int numeroChunk=0;
        readn((*sd),&numeroChunk,sizeof(int));
        int i=0;
        char tmpHashChunk[33];
        //preparo le strutture chunk da inviare sul socket
        struct chunk * chks = malloc(sizeof(struct chunk)*numeroChunk);
        struct chunk * tmpChunk = chks;
        for(i=0;i<numeroChunk;i++){
            readn((*sd),tmpHashChunk,sizeof(char)*33);
            strcpy(tmpChunk->ID,tmpHashChunk);
            strcpy(tmpChunk->IDF,hashFile);
            tmpChunk=tmpChunk+1;
        }
        // a questo punto chiamo la funzione request_receive_chunk_1 per
        // trovare i dati sull'HD ed inviarli a socket
        tmpChunk=chks;
        request_receive_chunk_1(tmpChunk,numeroChunk,sd);

        return 1;
    }
    
    //testata e funzionante
    int handle_receive_chunk_1(int *sd){
       // int operation = 0;
     //   readn((*sd),&operation,sizeof(int));
        //bisogna leggere l'hash del file
        char IDF[33];
        readn((*sd),IDF,sizeof(char)*32);
        IDF[32]='\0';
        //bisogna leggere il numero di chunk
        int nChunk=0;
        readn((*sd),&nChunk,sizeof(int));
        //bisogna leggere le strutture chunk e salvarli
        int i=0;
        //creo la directory specificata
        createDirectory(pconf->sharedir, IDF);
        char percorso[strlen(pconf->sharedir)+64];
        memset(percorso,0,strlen(pconf->sharedir)+64);
        FILE *fp;
        for(i=0;i<nChunk;i++){
            strcat(percorso,pconf->sharedir);
            strcat(percorso,IDF);
            struct chunk *chk = malloc(sizeof(struct chunk));
            readn((*sd),chk,sizeof(struct chunk));
            //salva il file su HD grazie a IDF posso ritrovare facilmente
            //la directory
            strcat(percorso,"/");
            strcat(percorso,chk->ID);
            if((fp=fopen(percorso,"wb"))==NULL){
                perror("Errore apertura file\n");
                exit(-1);
            }
            memset(percorso,0,strlen(pconf->sharedir)+64);
            fwrite(chk->chk,chk->size,1,fp);
            fclose(fp);
            free(chk);
        }
        close_socket(sd);
        return 1;
    }
    //tale richiesta arriva dal superpeer
    //testata e funzionante
    int handle_delete_file(int *sd){

        char IDF[33];

        readn((*sd),IDF,sizeof(char)*32);

        IDF[32]='\0';

        //bisogna verificare se la cartella da cancellare si trova in fileCompleti
        //oppure in fileIncompleti
        //il primo test è sui fileCompleti
        char *path = calloc(strlen(pconf->sharedir)+33+13+1,1);
        strcat(strcat(strcat(strcat(path,pconf->sharedir),"fileCompleti/"),IDF),"/");
        int existDir = existDirectory(path);
        free(path);
        if(existDir==1){
            path = calloc(strlen(pconf->sharedir)+13+1,1);
            strcat(strcat(path,pconf->sharedir),"fileCompleti/");
            removeDirectory(path, IDF);
            free(path);
        }
        else{
            path=calloc(strlen(pconf->sharedir)+15+1,1);
            strcat(strcat(path,pconf->sharedir),"fileIncompleti/");
            existDir = existDirectory(path);
            if(existDir==1){
                removeDirectory(path, IDF);
            }
            else return -1;
            
            free(path);
        }

        close_socket(sd);

        return 1;
    }
    